#define VERBOSE
using System;
using System.Drawing;
using System.Linq;
using RayDen.Library.Components.SystemComponents;
using RayDen.Library.Core;
using RayDen.Library.Core.Primitives;
using RayDen.Library.Data;
using RayDen.Library.Data.Imaging;
using RayDen.RayEngine.Core.Types;

namespace RayDen.RayEngine
{
    public class AdaptiveImageFilm : ImageFilmBase
    {
        private float threshold = 300;

        protected readonly SamplePixel[] adaptiveImage;
        protected float[] Error;
        FastRandom rnd = new FastRandom();

        protected double meanVariance, imageVariance = double.PositiveInfinity;

        protected ulong pixelCount;

        public AdaptiveImageFilm(int width, int height)
            : base(width, height)
        {
            this.adaptiveImage = new SamplePixel[width * height];
            this.Error = new float[width * height];
            this.meanVariance = 0.0;
            this.pixelCount = 0;
        }

        public override void SavePNG(string fileName, string[] watermark)
        {
            base.SavePNG(fileName, watermark.Union(new string[] { string.Format("Image Variance {0:F10}", (imageVariance)) }).ToArray());
            var pixelData = this.statsImage.SelectMany(pix => pix.Radiance.ToArray()).ToArray();

            using (var img = new Bitmap(this.Width, this.Height))
            {
                for (int y = 0; y < this.Height; y++)
                {
                    for (int x = 0; x < this.Width; x++//,ptr += 3
                        )
                    {
                        var offset = (x + y * this.Width) * 3;
                        img.SetPixel(x, Height - y - 1,
                                     new RgbSpectrum(pixelData[offset], pixelData[offset + 1], pixelData[offset + 2])
                                         .ToColor());
                    }
                }
                img.Save(fileName + "stats.png");
            }

            /*
            pixelData = this.pixelData.SelectMany(pix => pix.Radiance.ToArray()).ToArray();

            using (var img = new Bitmap(this.Width, this.Height))
            {
                for (int y = 0; y < this.Height; y++)
                {
                    for (int x = 0; x < this.Width; x++//,ptr += 3
                        )
                    {
                        var offset = (x + y * this.Width) * 3;
                        img.SetPixel(x, Height - y - 1,
                                     new RgbSpectrum(pixelData[offset], pixelData[offset + 1], pixelData[offset + 2])
                                         .ToColor());
                    }
                }
                img.Save(fileName + ".pixeldata.png");
            }

            pixelData = this.adaptiveImage.SelectMany(pix => pix.Radiance.ToArray()).ToArray();

            using (var img = new Bitmap(this.Width, this.Height))
            {
                for (int y = 0; y < this.Height; y++)
                {
                    for (int x = 0; x < this.Width; x++//,ptr += 3
                        )
                    {
                        var offset = (x + y * this.Width) * 3;
                        img.SetPixel(x, Height - y - 1,
                                     new RgbSpectrum(pixelData[offset], pixelData[offset + 1], pixelData[offset + 2])
                                         .ToColor());
                    }
                }
                img.Save(fileName + ".adaptive.png");
            }*/
        }

        private readonly object oddLock = new object();

        public override string GetStats()
        {
            return string.Format("Image Variance {0:F10}", ((meanVariance / (Width*Height))));
        }

        public float[] GetStatImage()
        {
            return this.statsImage.SelectMany(item => item.Radiance.ToArray()).ToArray();
        }

        protected volatile byte odd = 0;
        protected override void SplatRadiance(ref RgbSpectrum radiance, int x, int y, float weight = 1.0f)
        {
#if VERBOSE
            try
            {
#endif

                if (rnd.NextDouble() >= 0.5)
                {
                    //lock (oddLock)
                    {
                        base.SplatRadiance(ref radiance, x, y, weight);
                        odd = 0;
                    }
                }
                else
                {
                    //lock (oddLock)
                    {
                        this.AddPixel(x, y, ref radiance, weight, adaptiveImage);
                        odd = 1;
                    }
                }

                //if (weight > 0f)
                {
                    this.statsImage[x + y * Width].Weight += 1f;

                    this.statsImage[x + y * Width].Radiance = this.GetHeat(this.statsImage[x + y * Width].Weight, threshold);
                }

                Error[x + y * Width] = Variance(x, y);


#if VERBOSE1
                if (!this.statsImage[x + y * Width].Radiance.IsBlack())
                {
                    this.AddPixel(x, y, ref this.statsImage[x + y * Width].Radiance, weight);
                    base.SplatRadiance(ref this.statsImage[x + y * Width].Radiance, x, y, weight);
                }
#endif

                pixelCount++;
#if VERBOSE
            }
            catch (Exception ex)
            {
                Tracer.TraceLine(ex.Message);
            }
#endif
        }


        private RgbSpectrum GetHeat(float heat, float thr)
        {
            if (heat < thr)
            {
                return new RgbSpectrum(0f, 0f, thr / heat);
            }
            else
                if (heat < thr * 2)
                {
                    return new RgbSpectrum(0f, thr / heat, 0f);
                }
                else
                    if (heat < thr * 3)
                    {
                        return new RgbSpectrum(thr / heat, 0f, 0f);
                    }
            return new RgbSpectrum(1f - (thr / heat), thr / heat, thr / heat);
        }

        public float SVariance(int imageX, int imageY)
        {
            return Error[imageX + imageY * Width];
        }


        public void StatsRect(int xs, int ys, int wt, int ht, int colorIndex)
        {
            RgbSpectrum col = RgbSpectrum.UnitSpectrum();
            //new RgbSpectrum(1f, 0.2f+ rnd.NextFloat()*0.5f, 1f);
            var w = Math.Min(Width - 1, wt);
            var h = Math.Min(Height - 1, ht);
            for (int x = xs; x < MathLab.Clamp(xs + w, 0, Width - 1); x++)
            {
                statsImage[x + ys * Width] = new SamplePixel()
                    {
                        Radiance = col,
                        Weight = 1f
                    };
                statsImage[x + (MathLab.Clamp(ys + h, 0, Height - 1) * Width)] = new SamplePixel()
                {
                    Radiance = col,
                    Weight = 1f
                };
            }

            for (int y = ys; y < MathLab.Clamp(ys + h, 0, Height - 1); y++)
            {
                statsImage[xs + y * Width] = new SamplePixel()
                {
                    Radiance = col,
                    Weight = 1f
                };
                statsImage[MathLab.Clamp(xs + w, 0, Width - 1) + y * Width] = new SamplePixel()
                {
                    Radiance = col,
                    Weight = 1f
                };
            }
        }

        public void CleanStats()
        {
            this.statsImage = new SamplePixel[Width * Height];
        }

        public float Variance(int imageX, int imageY)
        {
            var i = imageX + imageY * Width;
            var A = new RgbSpectrum(pixelData[i].Radiance);
            var B = new RgbSpectrum(adaptiveImage[i].Radiance);

            var I = new RgbSpectrum((A.c1 + B.c1) * 0.5f,
                                    (A.c2 + B.c2) * 0.5f,
                                    (A.c3 + B.c3) * 0.5f);
            var e = Math.Abs(I.c1 - A.c1) + Math.Abs(I.c2 - A.c2) + Math.Abs(I.c3 - A.c3);
            if (I.IsBlack())
                return 0;
            e /= MathLab.Sqrt(I.c1 + I.c2 + I.c3);
            return e;
        }

        public bool Black(int imageX, int imageY)
        {
            var i = imageX + imageY * Width;
            var A = new RgbSpectrum(pixelData[i].Radiance);
            var B = new RgbSpectrum(adaptiveImage[i].Radiance);

            return A.IsBlack() && B.IsBlack();
        }

        public float RelativeVariance(int imageX, int imageY)
        {
            var i = imageX + imageY * Width;
            var A = new RgbSpectrum(pixelData[i].Radiance);
            var B = new RgbSpectrum(adaptiveImage[i].Radiance);
            var I = new RgbSpectrum((A.c1 - B.c1) * (A.c1 - B.c1),
                                    (A.c2 - B.c2) * (A.c2 - B.c2),
                                    (A.c3 - B.c3) * (A.c3 - B.c3));
            return I.Average;
        }

        protected override void UpdateFrameBuffer()
        {
            base.UpdateFrameBuffer();
            meanVariance = this.Error.Sum(p => p);
            imageVariance = meanVariance/(Width*Height);
        }

        protected internal override void ComputePixel(int i, float w = 1.0f)
        {
            RgbSpectrum.Gamma = true;

            var sampledPixelRadiance = new RgbSpectrum(pixelData[i].Radiance.c1 * 0.5f + adaptiveImage[i].Radiance.c1 * 0.5f,
                pixelData[i].Radiance.c2 * 0.5f + adaptiveImage[i].Radiance.c2 * 0.5f,
                pixelData[i].Radiance.c3 * 0.5f + adaptiveImage[i].Radiance.c3 * 0.5f);
            var sampledPixelWeight = pixelData[i].Weight * 0.5f + adaptiveImage[i].Weight * 0.5f;

            float weight = sampledPixelWeight;
            if (weight > 0f)
            {
                var f = w / weight;

                var pix = new RgbSpectrum
                {
                    c1 = Radiance2PixelFloat(sampledPixelRadiance.c1 * (f)),
                    c2 = Radiance2PixelFloat(sampledPixelRadiance.c2 * (f)),
                    c3 = Radiance2PixelFloat(sampledPixelRadiance.c3 * (f))

                };

                //var pix = (f*pixelData[i].Radiance).ToRgb();
                this.SetPixel(i, ref pix);
            }
        }



        public override void Consume(PathSamplerBase path)
        {
            throw new NotImplementedException();
        }

        public float[] GetVarianceImage()
        {
            return this.Error.SelectMany(i => new RgbSpectrum(i).ToArray()).ToArray();
        }
    }
}